﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Threading;
using System.Xml.Linq;
using MessageBox = System.Windows.MessageBox;

namespace _508ComplianceValidator
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        private bool shouldCheckForSkipLink = Convert.ToBoolean(ConfigurationManager.AppSettings["CheckForSkipLink"]);
        public static Collection<CheckableItem> SelectedFiles = new Collection<CheckableItem>();
        private readonly Collection<Rule> _rulesToCheck = new Collection<Rule>();
        private readonly StringBuilder _violationMessages = new StringBuilder();
        private readonly BackgroundWorker _worker = new BackgroundWorker();
        private ObservableCollection<CheckableItem> _allItems = new ObservableCollection<CheckableItem>();
        private int _violationCount;

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            try
            {
                DataContext = this;
                InitializeComponent();
                _worker.DoWork += worker_DoWork;
                _worker.RunWorkerCompleted += worker_RunWorkerCompleted;
                ReadRules();
            }
            catch (Exception ex)
            {
                MessageBox.Show("We have encountered an error. " + ex.Message + ex.StackTrace,
                                "508 Compliance Validator",
                                MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Gets or sets all items.
        /// </summary>
        /// <value>
        /// All items.
        /// </value>
        public ObservableCollection<CheckableItem> AllItems
        {
            get { return _allItems; }
            set { _allItems = value; }
        }

        /// <summary>
        /// Handles the RunWorkerCompleted event of the worker control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs"/> instance containing the event data.</param>
        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                //_violationMessages.Append(
                //    "********************************************************************************************************************************************************************************************************" +
                //    Environment.NewLine);
                File.WriteAllText("ViolationLog.csv", _violationMessages.ToString());
                MessageBox.Show(
                    "Found violations. Count " + _violationCount + Environment.NewLine + "ViolationLog.csv created at " +
                    Directory.GetCurrentDirectory(), "508 Compliance Validator",
                    MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show("We have encountered an error. " + ex.Message + ex.StackTrace,
                                "508 Compliance Validator",
                                MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Handles the DoWork event of the worker control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                foreach (CheckableItem file in SelectedFiles)
                {
                    string extension = Path.GetExtension(file.Value);
                    if (extension != null &&
                        (File.Exists(file.Value) && (extension.Equals(".aspx") || extension.Equals(".ascx"))))
                        ValidateHtmlFor508Compliance(file.Value);
                    Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                                           new UpdateProgressDelegate(UpdateProgress));
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("We have encountered an error. " + ex.Message + ex.StackTrace,
                                "508 Compliance Validator",
                                MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Handles the Click event of the btnBrowse control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void btnBrowse_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var folderBrowser = new FolderBrowserDialog { ShowNewFolderButton = true };
                folderBrowser.ShowDialog();
                if (string.IsNullOrEmpty(folderBrowser.SelectedPath)) return;
                SelectedFiles.Clear();
                AllItems.Clear();
                txtSelectedPath.Text = folderBrowser.SelectedPath;
                var parentItem = new CheckableItem
                                     {
                                         Value = folderBrowser.SelectedPath,
                                         DisplayText = Path.GetFileName(folderBrowser.SelectedPath)
                                     };
                CreateFolderHierarchy(folderBrowser.SelectedPath, parentItem);
                AllItems.Add(parentItem);
            }
            catch (Exception ex)
            {
                MessageBox.Show("We have encountered an error. " + ex.Message + ex.StackTrace,
                                "508 Compliance Validator",
                                MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Handles the Click event of the btnValidate control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void btnValidate_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (AllItems.Count == 0)
                {
                    MessageBox.Show("Browse to a folder where aspx or ascx files are stored. ",
                                    "508 Compliance Validator",
                                    MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }
                if (SelectedFiles.Count == 0)
                {
                    MessageBox.Show("Select files which need to be validated. ", "508 Compliance Validator",
                                    MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }
                _violationMessages.Clear();
                _violationCount = 0;
                pbProgress.Value = 1;
                pbProgress.Minimum = 1;
                pbProgress.Maximum = SelectedFiles.Count;
                _violationMessages.Append("File Name,Violation Message,Applies To Control,Attribute To Check,Rule Type" + Environment.NewLine);
                _worker.RunWorkerAsync();
            }
            catch (Exception ex)
            {
                MessageBox.Show("We have encountered an error. " + ex.Message + ex.StackTrace,
                                "508 Compliance Validator",
                                MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Creates the folder hierarchy.
        /// </summary>
        /// <param name="sourceDirectory">The source directory.</param>
        /// <param name="parentItem">The parent item.</param>
        private static void CreateFolderHierarchy(string sourceDirectory, CheckableItem parentItem)
        {
            foreach (string directory in Directory.GetDirectories(sourceDirectory))
            {
                var childItem = new CheckableItem { Value = directory, DisplayText = Path.GetFileName(directory) };
                parentItem.Children.Add(childItem);
                CreateFolderHierarchy(directory, childItem);
            }
            foreach (string file in Directory.GetFiles(sourceDirectory))
            {
                var childItem = new CheckableItem { Value = file, DisplayText = Path.GetFileName(file) };
                parentItem.Children.Add(childItem);
            }
        }

        /// <summary>
        /// Validates the HTML for 508 compliance.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        private void ValidateHtmlFor508Compliance(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentException("'fileName' must have a value.", "fileName");
            bool isSkipLinkFound = false;
            string content = File.ReadAllText(fileName);
            const RegexOptions options = RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase;
            var tagExpr = new Regex("(?<tag><[^%/](?:.*?)>)", options);
            var serverTagExpr = new Regex("<(?<tag>[a-z][a-z1-9]*):(?<type>[a-z][a-z1-9]*)", options);
            var htmlTagExpr = new Regex("<(?<type>[a-z][a-z1-9]*)", options);
            var attributeExpr = new Regex("(?<name>\\S+)=[\"']<?(?<value>(?:.?(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?",
                                          options);
            foreach (Match tagMatch in tagExpr.Matches(content))
            {
                Match serverTagMatch = serverTagExpr.Match(tagMatch.Value);

                if (!serverTagMatch.Success)
                    serverTagMatch = htmlTagExpr.Match(tagMatch.Value);
                if (!serverTagMatch.Success)
                    continue;
                if (shouldCheckForSkipLink && serverTagMatch.Groups["type"].Value.ToLower().Equals("a"))
                {
                    if (tagMatch.Value.ToLower().Contains("skip"))
                    {
                        isSkipLinkFound = true;
                    }
                }
                foreach (Rule rule in _rulesToCheck)
                {
                    bool rulePassed = false;
                    if (!rule.AppliesToControl.Equals(serverTagMatch.Groups["type"].Value.ToLower()))
                        continue;
                    foreach (
                        Match attributeMatch in
                            attributeExpr.Matches(tagMatch.Value).Cast<Match>().Where(
                                attributeMatch => attributeMatch.Success))
                    {
                        switch (rule.RuleCondition)
                        {
                            case RuleType.CheckForPresence:
                                if (attributeMatch.Groups["name"].Value.ToLower().Equals(rule.AttributeToCheck))
                                    rulePassed = true;
                                break;
                            case RuleType.CheckForExternalization:
                                if (attributeMatch.Groups["name"].Value.ToLower().Equals(rule.AttributeToCheck))
                                {
                                    if (attributeMatch.Groups["value"].Value.Contains("%="))
                                        rulePassed = true;
                                }
                                break;
                        }
                        if (rulePassed)
                            break;
                    }
                    if (!rulePassed)
                    {
                        switch (rule.RuleCondition)
                        {
                            case RuleType.CheckForPresence:
                                _violationMessages.Append(fileName + "," + tagMatch.Value.Replace(',', ' ') +
                                                          " must have a " + rule.AttributeToCheck + " field present.," + rule.AppliesToControl + "," + rule.AttributeToCheck + "," + rule.RuleCondition +
                                                          Environment.NewLine);
                                break;
                            case RuleType.CheckForExternalization:
                                _violationMessages.Append(fileName + "," + tagMatch.Value.Replace(',', ' ') +
                                                          " should have externalized value for " + rule.AttributeToCheck + " field.," + rule.AppliesToControl + "," + rule.AttributeToCheck + "," + rule.RuleCondition +
                                                          Environment.NewLine);
                                break;
                        }
                        _violationCount++;
                    }
                }
            }
            if (!isSkipLinkFound)
            {
                _violationMessages.Append(fileName + ",The page must have a Skip link." + Environment.NewLine);
                _violationCount++;
            }
        }

        /// <summary>
        /// Reads the rules.
        /// </summary>
        private void ReadRules()
        {
            IEnumerable<XElement> rules = from c in XElement.Load("Rules.xml").Descendants("Rule")
                                          select c;
            foreach (XElement rule in rules)
            {
                XAttribute appliesTo = rule.Attribute("AppliesTo");
                if (appliesTo == null) continue;
                XAttribute attributesToCheck = rule.Attribute("AttributeToCheck");
                if (attributesToCheck == null) continue;
                XAttribute ruleType = rule.Attribute("RuleCondition");
                if (ruleType == null) continue;
                _rulesToCheck.Add(new Rule
                                      {
                                          AppliesToControl = appliesTo.Value,
                                          AttributeToCheck = attributesToCheck.Value,
                                          RuleCondition = (RuleType)(Enum.Parse(typeof(RuleType), ruleType.Value))
                                      });
            }
            if (_rulesToCheck.Count == 0)
            {
                MessageBox.Show(
                    "There are no rules present in Rule file. Please add some rules and restart the application",
                    "508 Compliance Validator",
                    MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        /// <summary>
        /// Updates the progress.
        /// </summary>
        private void UpdateProgress()
        {
            pbProgress.Value++;
        }

        #region Nested type: UpdateProgressDelegate

        private delegate void UpdateProgressDelegate();

        #endregion
    }
}